﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Linq;

namespace tdgame {
    public class WorldBuilder {

        public enum MapType {
            Lakes,
			Islands,
            Plains,
            Hills,
            Mountainous,
            RoughHills,
			Perlin
        }

        public enum MapSize {
            Small = 500,
            Medium = 1000,
            Large = 1500,
            Huge = 2000
        }

		public enum CellType {
			Cell,
			Slope,
			Water
		}

		public enum SlopeDirection {
			North,
			South,
			East,
			West,
			Diagonal
		}

		public int KeyPointSpacing = 25,
			MinHeight = 120,
			MaxHeight = 120,
			KeyPointEffectRadius = 10,
			WorldSize = 100,
			MapSeed = 0,
			pOctaves = 1;
        public double ElevationIncrement = 0.0;
		public double pMinHeight=0.0f,
			pMaxHeight = 1.0f,
			pPersistance = 0.7f;
		public bool pBlur = false;
		public PerlinNoise.InterpolationMode pMode = PerlinNoise.InterpolationMode.None;

        public struct KeyPoint {
            public double X;
            public double Y;
            public double Z;
            public double EffectRadius;
            public double Weight;
            public KeyPoint(double x, double y, double z, double effectRadius, double weight) {
                X = x;
                Y = y;
                Z = z;
                EffectRadius = effectRadius;
                Weight = weight;
            }
        }

		public struct Cell {
			public int X;
			public int Z;

			public float BaseHeight;

			public bool IsWater;
			public float WaterDepth;

			public bool IsSlope;
			public SlopeDirection SlopeDirection;
			public float SlopeHeight;

			public bool HasTower;

			public int EdgeDistance;

			public bool Traversable(Pathfinder.MovementRange movement) {
				bool ret = true;
				if (HasTower)
					ret = false;
				else {
					switch (movement) {
						case Pathfinder.MovementRange.Air:
							if (BaseHeight > 220)
								ret = false;
							break;
						case Pathfinder.MovementRange.AirHover:
							if (BaseHeight > 194 || SlopeHeight > 8)
								ret = false;
							break;
						case Pathfinder.MovementRange.Ground:
							if (IsWater || SlopeHeight > 2)
								ret = false;
							break;
						case Pathfinder.MovementRange.GroundCrawler:
							if (IsWater || SlopeHeight > 4)
								ret = false;
							break;
						case Pathfinder.MovementRange.Hover:
							if (!IsWater && SlopeHeight > 2)
								ret = false;
							break;
						case Pathfinder.MovementRange.WaterSubmerged:
							if (!IsWater || WaterDepth <= 10)
								ret = false;
							break;
						case Pathfinder.MovementRange.WaterSurface:
							if (!IsWater || WaterDepth <= 2)
								ret = false;
							break;
					}
				}
				return ret;
			}
			//public Cell() {
			//    X = -1;
			//    Y = -1;
			//    BaseHeight = 0;
			//    IsWater = false;
			//    WaterDepth = 0;
			//    IsSlope = false;
			//    SlopeDirection = WorldBuilder.SlopeDirection.Diagonal;
			//    SlopeHeight = 0;
			//}
		}


        public List<KeyPoint> key_pts = new List<KeyPoint>();

		public Vector3[,] map;
		public Cell[,] cellMap;
		//PerlinNoise perlin;

        public WorldBuilder(int seed, MapSize mapSize, MapType mapType) {
            Random R = new Random(seed);
            KeyPointSpacing = 50;
            KeyPointEffectRadius = 100;
			WorldSize = (int)mapSize;


			bool custom = true;

			switch (mapType) {
				case MapType.Hills:
					MinHeight = 110;
					MaxHeight = 160;
					KeyPointEffectRadius = 120;
					break;
				case MapType.Lakes:
					MinHeight = 70;
					MaxHeight = 140;
					KeyPointEffectRadius = 150;
					break;
				case MapType.Mountainous:
					MinHeight = 120;
					MaxHeight = 300;
					KeyPointEffectRadius = 75;
					break;
				case MapType.Plains:
					MinHeight = 110;
					MaxHeight = 140;
					KeyPointEffectRadius = 200;
					break;
				case MapType.RoughHills:
					MinHeight = 110;
					MaxHeight = 230;
					KeyPointEffectRadius = 120;
					break;
				case MapType.Islands:
					MinHeight = 50;
					MaxHeight = 120;
					KeyPointEffectRadius = 70;
					break;
				case MapType.Perlin:
					pMinHeight = 0.2f;
					pMaxHeight = 0.5f;
					pPersistance = 0.6f;
					pOctaves = 8;
					pBlur = true;
					pMode = PerlinNoise.InterpolationMode.Cosine;
					custom = false;
					break;
			}

            ElevationIncrement = 2;
            MapSeed = seed;
			int min = -WorldSize / 2, max = WorldSize / 2;
			map = new Vector3[WorldSize / 5, WorldSize / 5];


			if (custom) {
				for (int x = min - KeyPointSpacing; x <= max + KeyPointSpacing; x += KeyPointSpacing) {
					for (int y = min - KeyPointSpacing; y <= max + WorldSize + KeyPointSpacing; y += KeyPointSpacing) {

						int _x = R.Next(x, x + KeyPointSpacing),
							_y = R.Next(MinHeight, MaxHeight),
							_z = R.Next(y, y + KeyPointSpacing),
							radius = KeyPointEffectRadius;

						double weight = 1.0;
						if (mapType == MapType.Islands) {
							if (_y > 100)
								radius = 120;
						}
						key_pts.Add(new KeyPoint(_x,
							_y,
							_z,
							radius,
							weight));
					}
				}


				for (int x = 0; x <= map.GetUpperBound(0); x++) {
					Parallel.For(0, map.GetUpperBound(1) + 1, y => {
						map[x, y] = new Vector3(min + (x * 5), GetHeightAtPoint(min + (x * 5), min + (y * 5)), min + (y * 5));
					});
				}
			}
			else {
				double[,] pMap = PerlinNoise.GetPerlinNoise(WorldSize / 5, WorldSize / 5, pMinHeight, pMaxHeight, pPersistance, pOctaves, pMode, pBlur, seed);
				for (int x = 0; x <= map.GetUpperBound(0); x++) {
					Parallel.For(0, map.GetUpperBound(1) + 1, y => {
                        //float f = perlin.GetRandomHeight(x * 5, y * 5, pMaxHeight, pFrequency, pAmplitude, pPersistance, pOctaves);
                        //f = ((f + 1f) * 0.5f);

                        map[x, y] = new Vector3(min + (x * 5), 300f * (float)pMap[x,y], min + (y * 5));
                         if (ElevationIncrement != 0.0)
                            map[x, y].Y -= map[x, y].Y % (float)ElevationIncrement;
                       
					});
				}


			}


			cellMap = new Cell[map.GetUpperBound(0), map.GetUpperBound(1)];


			for (int x = 0; x < map.GetUpperBound(0); x++) {
				for (int y = 0; y < map.GetUpperBound(0); y++) {
					Vector3 ne = map[x,y],
						nw = map[x+1,y],
						se = map[x,y+1],
						sw = map[x+1,y+1];

					cellMap[x, y].X = x;
					cellMap[x, y].Z = y;

					cellMap[x, y].EdgeDistance = -2;
					cellMap[x, y].HasTower = false;
					cellMap[x, y].BaseHeight = (float)(new List<double> { ne.Y, nw.Y, se.Y, sw.Y }).Min();
					float heightmax = (float)(new List<double> { ne.Y, nw.Y, se.Y, sw.Y }).Max();

					if (cellMap[x,y].BaseHeight < 100f) {
						cellMap[x, y].IsWater = true;

						cellMap[x, y].WaterDepth = (heightmax <= 100 ? 100 - heightmax : 0);

					}

					if (cellMap[x, y].BaseHeight != heightmax) {
						cellMap[x, y].IsSlope = true;
						cellMap[x, y].SlopeHeight = heightmax - cellMap[x, y].BaseHeight;
						cellMap[x, y].EdgeDistance = -1;
					}

				}
			}

			int pass = -1;
			bool filled = false;
			while (!filled) {
				List<Point> pts = GetPassPoints(pass);
				if (pts.Count == 0)
					filled = true;
				else {
					pass++;
					foreach (Point pt in pts) {
						if (pt.X > 0)
							if (cellMap[pt.X - 1, pt.Y].EdgeDistance == -2)
								cellMap[pt.X - 1, pt.Y].EdgeDistance = pass;
						if (pt.X < cellMap.GetUpperBound(0))
							if (cellMap[pt.X + 1, pt.Y].EdgeDistance == -2)
								cellMap[pt.X + 1, pt.Y].EdgeDistance = pass;
						if (pt.Y > 0)
							if (cellMap[pt.X, pt.Y - 1].EdgeDistance == -2)
								cellMap[pt.X, pt.Y - 1].EdgeDistance = pass;
						if (pt.Y < cellMap.GetUpperBound(0))
							if (cellMap[pt.X, pt.Y + 1].EdgeDistance == -2)
								cellMap[pt.X, pt.Y + 1].EdgeDistance = pass;
					}
				}
			}
        }

		private List<Point> GetPassPoints(int pass) {
			List<Point> ret = new List<Point>();
			for (int x = 0; x <= cellMap.GetUpperBound(0); x++) {
				for (int y = 0; y <= cellMap.GetUpperBound(0); y++) {
					if (cellMap[x, y].EdgeDistance == pass)
						ret.Add(new Point(x, y));
				}
			}
			return ret;
		}

        public float GetHeightAtPoint(double x, double z) {
            double totHeight = 0.0, ret = 127.0, count = 0.0;

            for (int i = 0; i < key_pts.Count; i++) {
                KeyPoint p = key_pts[i];
				
                double dist = MathExtras.Calculate2DDistance(x, z, p.X, p.Z);
                if (dist <= p.EffectRadius) {
                    double factor = (((p.EffectRadius - dist) / p.EffectRadius) * p.Weight);
                    totHeight += (p.Y * factor);
                    count += factor;
                }
            }

            if (count > 0)
                ret = totHeight / count;
            if (ElevationIncrement != 0.0)
                ret -= ret % ElevationIncrement;
            return (float)ret;
        }
    }
}
